Array.prototype.removeByValue = function(val) {
    for(var i=0; i<this.length; i++) {
        if(this[i] == val) {
            this.splice(i, 1);
            break;
        }
    }
    return this;
};

Array.prototype.addAll = function(anotherArr) {
    for(var i=0; i<anotherArr.length; i++) {
        this.push(anotherArr[i]);
    }
    return this;
};

Array.prototype.removeAll = function(anotherArr) {
    for(var i=0; i<anotherArr.length; i++) {
        this.removeByValue(anotherArr[i]);
    }
    return this;
};

function powerset(arr) {
    var ps = new Array(new Array());
    for (var i=0; i < arr.length; i++) {
        // we modify the ps array in the next loop,
        // so can't use the ps.length property directly in the loop condition.
        var current_length = ps.length;
        for (var j = 0; j < current_length; j++) {
            ps.push(ps[j].concat(arr[i]));
        }
    }
    return ps;
}
//*************************************************************************************************************//
//*************************************************************************************************************//
//*************************************************************************************************************//

var SUIT = {};
SUIT.SPADES = "♠";
SUIT.CLUBS = "♣";
SUIT.DIAMONDS = "♦";
SUIT.HEARTS = "♥";

function Card(rank, suit) {

    this.getRank = function() {
        return rank;
    };

    this.getSuit = function() {
        return suit;
    };

    this.toString = function() {
        var rankStr;
        switch (this.getRank()) {
            case 1:
                rankStr = "A";
                break;
            case 11:
                rankStr = "J";
                break;
            case 12:
                rankStr = "Q";
                break;
            case 13:
                rankStr = "K";
                break;
            default:
                rankStr = this.getRank();
                break;
        }
        return rankStr + this.getSuit();
    };
}

Card.SPADES_A = new Card(1, "♠");
Card.SPADES_2 = new Card(2, "♠");
Card.SPADES_3 = new Card(3, "♠");
Card.SPADES_4 = new Card(4, "♠");
Card.SPADES_5 = new Card(5, "♠");
Card.SPADES_6 = new Card(6, "♠");
Card.SPADES_7 = new Card(7, "♠");
Card.SPADES_8 = new Card(8, "♠");
Card.SPADES_9 = new Card(9, "♠");
Card.SPADES_10 = new Card(10, "♠");
Card.SPADES_J = new Card(11, "♠");
Card.SPADES_Q = new Card(12, "♠");
Card.SPADES_K = new Card(13, "♠");

Card.CLUBS_A = new Card(1, "♣");
Card.CLUBS_2 = new Card(2, "♣");
Card.CLUBS_3 = new Card(3, "♣");
Card.CLUBS_4 = new Card(4, "♣");
Card.CLUBS_5 = new Card(5, "♣");
Card.CLUBS_6 = new Card(6, "♣");
Card.CLUBS_7 = new Card(7, "♣");
Card.CLUBS_8 = new Card(8, "♣");
Card.CLUBS_9 = new Card(9, "♣");
Card.CLUBS_10 = new Card(10, "♣");
Card.CLUBS_J = new Card(11, "♣");
Card.CLUBS_Q = new Card(12, "♣");
Card.CLUBS_K = new Card(13, "♣");

Card.DIAMONDS_A = new Card(1, "♦");
Card.DIAMONDS_2 = new Card(2, "♦");
Card.DIAMONDS_3 = new Card(3, "♦");
Card.DIAMONDS_4 = new Card(4, "♦");
Card.DIAMONDS_5 = new Card(5, "♦");
Card.DIAMONDS_6 = new Card(6, "♦");
Card.DIAMONDS_7 = new Card(7, "♦");
Card.DIAMONDS_8 = new Card(8, "♦");
Card.DIAMONDS_9 = new Card(9, "♦");
Card.DIAMONDS_10 = new Card(10, "♦");
Card.DIAMONDS_J = new Card(11, "♦");
Card.DIAMONDS_Q = new Card(12, "♦");
Card.DIAMONDS_K = new Card(13, "♦");

Card.HEARTS_A = new Card(1, "♥");
Card.HEARTS_2 = new Card(2, "♥");
Card.HEARTS_3 = new Card(3, "♥");
Card.HEARTS_4 = new Card(4, "♥");
Card.HEARTS_5 = new Card(5, "♥");
Card.HEARTS_6 = new Card(6, "♥");
Card.HEARTS_7 = new Card(7, "♥");
Card.HEARTS_8 = new Card(8, "♥");
Card.HEARTS_9 = new Card(9, "♥");
Card.HEARTS_10 = new Card(10, "♥");
Card.HEARTS_J = new Card(11, "♥");
Card.HEARTS_Q = new Card(12, "♥");
Card.HEARTS_K = new Card(13, "♥");

Card.cardsBySuit = {
    "♠":[Card.SPADES_A, Card.SPADES_2, Card.SPADES_3, Card.SPADES_4, Card.SPADES_5, Card.SPADES_6, Card.SPADES_7, Card.SPADES_8, Card.SPADES_9, Card.SPADES_10, Card.SPADES_J, Card.SPADES_Q, Card.SPADES_K],
    "♣":[Card.CLUBS_A, Card.CLUBS_2, Card.CLUBS_3, Card.CLUBS_4, Card.CLUBS_5, Card.CLUBS_6, Card.CLUBS_7, Card.CLUBS_8, Card.CLUBS_9, Card.CLUBS_10, Card.CLUBS_J, Card.CLUBS_Q, Card.CLUBS_K],
    "♦":[Card.DIAMONDS_A, Card.DIAMONDS_2, Card.DIAMONDS_3, Card.DIAMONDS_4, Card.DIAMONDS_5, Card.DIAMONDS_6, Card.DIAMONDS_7, Card.DIAMONDS_8, Card.DIAMONDS_9, Card.DIAMONDS_10, Card.DIAMONDS_J, Card.DIAMONDS_Q, Card.DIAMONDS_K],
    "♥":[Card.HEARTS_A, Card.HEARTS_2, Card.HEARTS_3, Card.HEARTS_4, Card.HEARTS_5, Card.HEARTS_6, Card.HEARTS_7, Card.HEARTS_8, Card.HEARTS_9, Card.HEARTS_10, Card.HEARTS_J, Card.HEARTS_Q, Card.HEARTS_K]
};
Card.suits = ["♠", "♣", "♦", "♥"];
Card.rankStrings = {
    "A":1,
    "2":2,"3":3,"4":4,"5":5,"6":6,"7":7,"8":8,"9":9,"10":10,
    "J":11,
    "Q":12,
    "K":13
};
Card.cardOf = function(rank, suit) {
    if (Card.suits.indexOf(suit) == -1) {
        throw "Invalid suit " + suit;
    }
    if(rank < 1 || rank > 13) {
        throw "Invalid rank " + rank;
    }

    var cardsWithSuit = Card.cardsBySuit[suit];
    for(var i = 0; i < cardsWithSuit.length; i++) {
        if(cardsWithSuit[i].getRank() == rank) {
            return cardsWithSuit[i];
        }
    }
};

Card.RANK_COMPARATOR = function(card1, card2) {
    return card1.getRank() - card2.getRank();
};


Card.decodeString = function(str) {
    if (str === "") {
        return [];
    }

    var cardsStrs = str.trim().split(" ");
    var result = [];
    for(var i = 0; i < cardsStrs.length; i++) {
        var suitStr = cardsStrs[i].substring(cardsStrs[i].length - 1);
        var rankStr = cardsStrs[i].substring(0, cardsStrs[i].length - 1);
        result.push(Card.cardOf(Card.rankStrings[rankStr], suitStr));
    }
    return result;
};
Card.encodeString = function(cards) {
    var str = "";
    for(var i = 0; i < cards.length; i++) {
        str += cards[i].toString() + " ";
    }
    return str.trim();
};

var CONST = {};
CONST.minNumOfCardInPhom = 3;

function Phom(cards, type) {

    var rank = 0;
    for(var i = 0; i < cards.length; i++) {
        rank += cards[i].getRank();
    }

    if (cards.length < CONST.minNumOfCardInPhom) {
        throw "Invalid phom " + cards.toString();
    }
    this.size = function () {
        return cards.length;
    };

    this.rank = function() {
        return rank;
    };

    this.getCards = function() {
        return cards;
    };

    this.getType = function() {
        return type;
    };

    this.intersec = function(anotherPhom) {
        return Utils.intersection(this.getCards(), anotherPhom.getCards());
    };

    this.toString = function() {
        return "Phom[" + cards.toString() + "]";
    };

    this.buildAllPossibleNonIntersec = function(intersecPoints, takenCards) {
        if (this.size() > CONST.minNumOfCardInPhom) {
            return this.getType().buildAllPossibleNonInterec(this, intersecPoints, takenCards);
        } else {
            var result = new Array();
            result.push(this);
            return result;
        }
    }
}
var STRAIGHT = {};
STRAIGHT.getBestPhom = function(cards) {
    var cardsBySuit = {};
    for(var i = 0; i < cards.length; i++) {
        if (cardsBySuit[cards[i].getSuit()] == undefined) {
            cardsBySuit[cards[i].getSuit()] = new Array();
        }
        cardsBySuit[cards[i].getSuit()].push(cards[i]);
    }

    var result = new Array();
    for(key in cardsBySuit) {
        if (cardsBySuit[key].length < CONST.minNumOfCardInPhom) continue;
        cardsBySuit[key].sort(Card.RANK_COMPARATOR);
        var firstCard = cardsBySuit[key][0];
        var straightCards = new Array();
        straightCards.push(firstCard);
        for(i = 1; i < cardsBySuit[key].length; i++) {
            if(cardsBySuit[key][i].getRank() != firstCard.getRank() + 1) {
                if (straightCards.length >= CONST.minNumOfCardInPhom) {
                    result.push(new Phom(straightCards, STRAIGHT));
                }
                straightCards = new Array();
            }

            straightCards.push(cardsBySuit[key][i]);
            firstCard = cardsBySuit[key][i];
        }
        if (straightCards.length >= CONST.minNumOfCardInPhom) {
            result.push(new Phom(straightCards, STRAIGHT));
        }
    }

    return result;
};
STRAIGHT.buildAllPossibleNonInterec = function(phom, cardsIntersecPoint, takenCards) {
    var realIntersec = Utils.intersection(phom.getCards(), cardsIntersecPoint);
    var takenIntersec = Utils.intersection(phom.getCards(), takenCards);

    if(takenIntersec.length > 0) {
        var allPossiblePhomsWithTakenCards = Utils.allPossibleWithTakenCards(phom, takenIntersec, STRAIGHT);
        var result = new Array();
        for (var i = 0; i < allPossiblePhomsWithTakenCards.length; i++) {
            var withTakenPhom1 = allPossiblePhomsWithTakenCards[i];
            var localIntersec = Utils.intersection(withTakenPhom1.getCards(), realIntersec);

            for(var j = 0; j < allPossiblePhomsWithTakenCards.length; j++) {
                if (i != j) {
                    var withTakenPhom2 = allPossiblePhomsWithTakenCards[j];
                    localIntersec.addAll(withTakenPhom1.intersec(withTakenPhom2));
                }
            }
            result.addAll(Utils.allPossibleNonIntersec(withTakenPhom1, localIntersec, STRAIGHT));
        }
        return result;
    } else {
        return Utils.allPossibleNonIntersec(phom, realIntersec, STRAIGHT);
    }
};
STRAIGHT.bestInclude = function(cards, card) {
    var sameSuitCards = new Array();
    sameSuitCards.push(card);
    for(var i = 0; i < cards.length; i++) {
        if (cards[i].getSuit() === card.getSuit()) {
            sameSuitCards.push(cards[i]);
        }
    }

    var phoms = STRAIGHT.getBestPhom(sameSuitCards);
    if (phoms.length == 1) {
        return phoms[0];
    }

    return null;
};

var SAME_RANK = {};
SAME_RANK.getBestPhom = function(cards) {
    var cardsRankMap = {};
    for (var i =0; i < cards.length; i++) {
        if (cardsRankMap[cards[i].getRank()] == undefined) {
            cardsRankMap[cards[i].getRank()] = new Array();
        }
        cardsRankMap[cards[i].getRank()].push(cards[i]);
    }

    var result = new Array();
    for (key in cardsRankMap) {
        if (cardsRankMap[key].length < CONST.minNumOfCardInPhom) {
            continue;
        }
        result.push(new Phom(cardsRankMap[key], SAME_RANK));
    }

    return result;
};
SAME_RANK.buildAllPossibleNonInterec = function(phom, cardsIntersecPoint, takenCards) {
    var realIntersec = Utils.intersection(phom.getCards(), cardsIntersecPoint);
    var takenIntersec = Utils.intersection(phom.getCards(), takenCards);

    if(takenIntersec.length > 0) {
        var allPossiblePhomsWithTakenCards = Utils.allPossibleWithTakenCards(phom, takenIntersec, SAME_RANK);
        var result = new Array();
        for (var i = 0; i < allPossiblePhomsWithTakenCards.length; i++) {
            var withTakenPhom1 = allPossiblePhomsWithTakenCards[i];
            var localIntersec = Utils.intersection(withTakenPhom1.getCards(), realIntersec);

            for(var j = 0; j < allPossiblePhomsWithTakenCards.length; j++) {
                if (i != j) {
                    var withTakenPhom2 = allPossiblePhomsWithTakenCards[j];
                    localIntersec.addAll(withTakenPhom1.intersec(withTakenPhom2));
                }
            }
            result.addAll(Utils.allPossibleNonIntersec(withTakenPhom1, localIntersec, SAME_RANK));
        }
        return result;
    } else {
        return Utils.allPossibleNonIntersec(phom, realIntersec, SAME_RANK);
    }
};
SAME_RANK.bestInclude = function(cards, card) {
    var sameRankCards = new Array();
    sameRankCards.push(card);
    for(var i = 0; i < cards.length; i++) {
        if (cards[i].getRank() == card.getRank()) {
            sameRankCards.push(cards[i]);
        }
    }

    var phoms = SAME_RANK.getBestPhom(sameRankCards);
    if (phoms.length == 1) {
        return phoms[0];
    }
    return null;
};

var Utils = {};
Utils.allPossibleNonIntersec = function(phom, intersecCards, sameRankOrStraight) {
    var result = new Array();
    var powerSetIntersecCards = powerset(intersecCards);
    for(var i = 0; i <  powerSetIntersecCards.length; i++) {
        var phomCopyCards = phom.getCards().slice(0);
        phomCopyCards.removeAll(powerSetIntersecCards[i]);
        result.addAll(sameRankOrStraight.getBestPhom(phomCopyCards));
    }

    return result;
};
Utils.allPossibleWithTakenCards = function(phom, takenCards, sameRankOrStraight) {
    var phomCards = phom.getCards().slice(0);
    phomCards.removeAll(takenCards);
    var result = new Array();
    for(var i = 0; i < takenCards.length; i++) {
        var bestPhomIncludeTakenCard = sameRankOrStraight.bestInclude(phomCards, takenCards[i]);
        if (bestPhomIncludeTakenCard != null) {
            result.push(bestPhomIncludeTakenCard);
        }
    }
    return result;
};
Utils.intersection = function(arr1, arr2) {
    var result = new Array();
    for(var i = 0; i < arr1.length; i++) {
        if(arr2.indexOf(arr1[i]) != -1) {
            result.push(arr1[i]);
        }
    }
    return result;
};

function buildAllPossibleCombinePhoms(cards, takenCards) {
    var allSameRank = SAME_RANK.getBestPhom(cards);
    var allStraight = STRAIGHT.getBestPhom(cards);

    var intersecCardsMap = {};
    var phomsToBuild = new Array();
    phomsToBuild.addAll(allSameRank);
    phomsToBuild.addAll(allStraight);

    for(var i = 0; i < allStraight.length; i++) {
        intersecCardsMap[allStraight[i]] = new CardSet();
    }

    for(var j = 0; j < allSameRank.length; j++) {
        intersecCardsMap[allSameRank[j]] = new CardSet();
    }

    for(i = 0; i < allStraight.length; i++) {
        for(j = 0; j < allSameRank.length; j++) {
            var intersec = allStraight[i].intersec(allSameRank[j]);
            if (intersec.length > 0) {
                intersecCardsMap[allStraight[i]].addAll(intersec);
                intersecCardsMap[allSameRank[j]].addAll(intersec);
            }
        }
    }

    var allPossible = new Array();
    for(i =0; i < phomsToBuild.length; i++) {
        var intesec = intersecCardsMap[phomsToBuild[i]].values();
        allPossible.addAll(phomsToBuild[i].buildAllPossibleNonIntersec(intesec, takenCards));
    }

    var filteredAllPossible = [];
    for(i =0; i < allPossible.length; i++) {
        if(Utils.intersection(allPossible[i].getCards(), takenCards).length < 2) {
            filteredAllPossible.push(allPossible[i]);
        }
    }

    var allPossibleCombined = powerset(filteredAllPossible);
    var filteredAllPossibleCombined = [];
    for(i = 0; i < allPossibleCombined.length; i++) {
        if(allTakenCardHasPhomSolutionPredicate(allPossibleCombined[i], takenCards)) {
            filteredAllPossibleCombined.push(allPossibleCombined[i]);
        }
    }
    return filteredAllPossibleCombined;

    function allTakenCardHasPhomSolutionPredicate(phoms, takenCards) {
        if(takenCards.length == 0) return true;
        var intersectionCardsSet = new CardSet();
        for(var i = 0; i < phoms.length; i++) {
            intersectionCardsSet.addAll(Utils.intersection(phoms[i].getCards(), takenCards));
        }
        return intersectionCardsSet.values().length == takenCards.length;
    }
}

function bestPhom(cards, takenCards) {
    var allPossibleCombine = buildAllPossibleCombinePhoms(cards, takenCards);
    var solutions = [];
    for(var i = 0; i < allPossibleCombine.length; i++) {
        if (allPossibleCombine[i].length > 0) {
            var solution = new Solution(allPossibleCombine[i]);
            if(solution.hasNoIntersec()) {
                solutions.push(solution);
            }
        }
    }

    if (solutions.length > 0) {
        var min = solutions[0];
        var compareFunc = Solution.INTERSEC$asc_PHOM_SIZE$desc_CARD_RANK$desc_CARD_SIZE$desc_PHOM_TYPE$desc_COMPARATOR;
        for(i = 1; i < solutions.length; i++) {
            solution = solutions[i];
            if (compareFunc(solutions[i], min) < 0) {
                min = solutions[i];
            }
        }

        return min.getPhoms();
    }

    return [];
}

function getPhoms(cardStrs, takenCardStrs) {
    var best = bestPhom(Card.decodeString(cardStrs), Card.decodeString(takenCardStrs));
    var result = [];
    for(var i = 0; i < best.length; i++) {
        result.push(Card.encodeString(best[i].getCards()));
    }
    return result;
}

Solution.INTERSEC$asc_PHOM_SIZE$desc_CARD_RANK$desc_CARD_SIZE$desc_PHOM_TYPE$desc_COMPARATOR = function(o1, o2) {
    var compareINTERSEC = o1.getIntersecRank() - o2.getIntersecRank();
    if (compareINTERSEC != 0) {
        return compareINTERSEC;
    }

    var comparePHOM_SIZE = o1.getPhomsSize() - o2.getPhomsSize();
    if (comparePHOM_SIZE != 0) {
        return - comparePHOM_SIZE;
    }

    var compareCARD_RANK = o1.getTotalCardRank() - o2.getTotalCardRank();
    if (compareCARD_RANK != 0) {
        return - compareCARD_RANK;
    }

    var compareCARD_SIZE = o1.getTotalCardSize() - o2.getTotalCardSize();
    if (compareCARD_SIZE != 0) {
        return - compareCARD_SIZE;
    }

    var comparePHOM_TYPE = o1.getTotalPhomTypeRank() - o2.getTotalPhomTypeRank();
    if (comparePHOM_TYPE != 0) {
        return - comparePHOM_TYPE;
    }

    return 0;
};

function Solution(phoms) {
    var localIntersec = 0;
    var localTotalSize = 0;
    var localTotalRank = 0;
    var realIntersec = 0;
    var localTotalPhomTypeRank = 0;

    if(phoms.length > 1) {
        for(var i = 0; i < phoms.length - 1; i++) {
            var p1 = phoms[i];
            for (var j = i + 1; j < phoms.length; j++) {
                var p2 = phoms[j];
                var intersecSize = p1.intersec(p2).length;
                realIntersec += intersecSize;
                if (p1.getType() == p2.getType() && p1.getType() == SAME_RANK) {
                    localIntersec += intersecSize * 9;
                } else {
                    localIntersec += intersecSize;
                }
            }
        }

        for(i = 0; i < phoms.length; i++) {
            localTotalSize += phoms[i].size();
            localTotalRank += phoms[i].rank();
            localTotalPhomTypeRank += rankPhomType(phoms[i]);
        }
    } else {
        var phom = phoms[0];
        localIntersec = 0;
        localTotalSize = phom.size();
        localTotalRank = phom.rank();
        realIntersec = 0;
        localTotalPhomTypeRank = rankPhomType(phom);
    }

    var intersecRank = localIntersec;
    var totalRank = localTotalRank;
    var totalSize = localTotalSize - realIntersec;
    var totalPhomTypeRank = localTotalPhomTypeRank;

    this.getPhoms = function() {
        return phoms;
    };

    this.getPhomsSize = function() {
        return phoms.length;
    };

    this.hasNoIntersec = function() {
        return intersecRank == 0;
    };

    this.getIntersecRank = function() {
        return intersecRank;
    };

    this.getTotalCardSize = function() {
        return totalSize;
    };

    this.getTotalCardRank = function() {
        return totalRank;
    };

    this.getTotalPhomTypeRank = function() {
        return totalPhomTypeRank;
    };

    this.toString = function() {
        return phoms.join();
    };

    function rankPhomType(phom) {
        switch (phom.getType()) {
            case SAME_RANK:
                return phom.size();
            case STRAIGHT:
                return 1;
            default:
                throw "Unknown type " + phom.getType();
        }
    }
}

function CardSet() {
    var elements = {};

    this.add = function(e) {
        elements[e] = true;
    };

    this.addAll = function(arr) {
        for(var i = 0; i < arr.length; i++) {
            elements[arr[i]] = true;
        }
    };

    this.values = function() {
        var result = new Array();
        for(key in elements) {
            if(elements[key] === true) {
                result.push(Card.decodeString(key)[0]);
            }
        }
        return result;
    };
}